-module(rtps_cfw).

-include("rtps.hrl").
-include("rtps_cfw.hrl").

-export([new/0, new/2,
	 available_changes_max/1, irrelevant_change_set/2, lost_changes_update/2,
	 missing_changes/1, missing_changes_update/2, received_change_set/2]).

-export_type([cfw/0]).

-type cfw() :: #cfw{}.

%% The following functions are implementations of the functions from
%% sections 8.4.10.4.xx in which the logic is copied literally as far
%% as possible. Only the first 'unknown' and last 'lost' changes are
%% maintained. 'received' changes are not recorded individually but
%% deleted from the list of cfw's. So, the list of cfw's starts with
%% the last 'lost' change, a series of missing changes if any and the
%% first 'unknown' change.

-spec new() -> Cfws when
      Cfws :: nonempty_list(cfw()).
new() ->
    [#cfw{sequence_number = 0, status = lost}, #cfw{sequence_number = 1, status = unknown}].

-spec new(Min_seq_num, Max_seq_num) -> Cfws when
      Min_seq_num :: rtps:sequence_number(),
      Max_seq_num :: rtps:sequence_number(),
      Cfws :: nonempty_list(cfw()).
new(Min_seq_num, Max_seq_num) ->
    Lost = Min_seq_num - 1,
    Unknown = Max_seq_num + 1,
    [#cfw{sequence_number = Lost, status = lost},
     #cfw{sequence_number = Unknown, status = unknown}].

-spec available_changes_max(Cfws) -> Seq_num when
      Cfws :: nonempty_list(Cfw),
      Cfw :: cfw(),
      Seq_num :: rtps:sequence_number().
%% 8.4.10.4.2 available_changes_max This operation returns the maximum
%% SequenceNumber_t among the changes_from_writer changes in the RTPS
%% WriterProxy that are available for access by the DDS DataReader.
%%
%% TODO: remove the match on status = lost because the first element
%% in the list should always match that condition.
%%available_changes_max([#cfw{sequence_number = N0} | Cfws]) ->
available_changes_max([_Lost, #cfw{sequence_number = N} | _Cfws]) ->
    N - 1.

-spec irrelevant_change_set(Cfws1, Seq_num) -> Cfws2 when
      Cfws1 :: nonempty_list(Cfw),
      Cfw :: cfw(),
      Seq_num :: rtps:sequence_number(),
      Cfws2 :: nonempty_list(Cfw).
%% 8.4.10.4.3 irrelevant_change_set This operation modifies the status
%% of a ChangeFromWriter to indicate that the CacheChange with the
%% SequenceNumber_t ‘a_seq_num’ is irrelevant to the RTPS Reader.
%%
%% NB: Being irrelevant (which is not very nice) implies that the
%% change is to be considered received.
irrelevant_change_set(Cfws, Seq_num) ->
    received_change_set(Cfws, Seq_num).
    %% Cfws1 = grow(Cfws, Seq_num + 1),
    %% Cfw = #cfw{sequence_number = Seq_num, status = received, is_relevant = false},
    %% lists:keyreplace(Seq_num, #cfw.sequence_number, Cfws1, Cfw).

-spec lost_changes_update(Cfws1, First_available_seq_num) -> Cfws2 when
      Cfws1 :: nonempty_list(Cfw),
      Cfw :: cfw(),
      First_available_seq_num :: rtps:sequence_number(),
      Cfws2 :: nonempty_list(Cfw).
%% 8.4.10.4.4 lost_changes_update This operation modifies the status
%% stored in ChangeFromWriter for any changes in the WriterProxy whose
%% status is MISSING or UNKNOWN and have sequence numbers lower than
%% ‘first_available_seq_num.’ The status of those changes is modified
%% to LOST indicating that the changes are no longer available in the
%% WriterHistoryCache of the RTPS Writer represented by the RTPS
%% WriterProxy.
%%
%% status unknown is not used because such changes are just not in the
%% list.
lost_changes_update(Cfws, First_available_seq_num) ->
    Cfws1 = grow(Cfws, First_available_seq_num + 1),
    Fun = fun(#cfw{sequence_number = N, status = Status} = Cfw)
		when N < First_available_seq_num,
		     (Status =:= unknown orelse Status =:= missing) ->
		  Cfw#cfw{status = lost};
	     (Cfw) ->
		  Cfw
	  end,
    Cfws2 = lists:map(Fun, Cfws1),
    shrink(Cfws2).

-spec missing_changes(Cfws) -> Seq_nums when
      Cfws :: nonempty_list(Cfw),
      Cfw :: cfw(),
      Seq_nums :: [Seq_num],
      Seq_num :: rtps:sequence_number().
%% 8.4.10.4.5 missing_changes This operation returns the subset of
%% changes for the WriterProxy that have status ‘MISSING.’ The changes
%% with status ‘MISSING’ represent the set of changes available in the
%% HistoryCache of the RTPS Writer represented by the RTPS WriterProxy
%% that have not been received by the RTPS Reader.
missing_changes(Cfws) ->
    [N || #cfw{status = missing, sequence_number = N} <- Cfws].

-spec missing_changes_update(Cfws1, Last_available_seq_num) -> Cfws2 when
      Cfws1 :: nonempty_list(Cfw),
      Cfw :: cfw(),
      Last_available_seq_num :: rtps:sequence_number(),
      Cfws2 :: nonempty_list(Cfw).
%% 8.4.10.4.6 missing_changes_update This operation modifies the
%% status stored in ChangeFromWriter for any changes in the
%% WriterProxy whose status is UNKNOWN and have sequence numbers
%% smaller or equal to ‘last_available_seq_num.’ The status of those
%% changes is modified from UNKNOWN to MISSING indicating that the
%% changes are available at the WriterHistoryCache of the RTPS Writer
%% represented by the RTPS WriterProxy but have not been received by
%% the RTPS Reader.
missing_changes_update(Cfws, Last_available_seq_num) ->
    Cfws1 = grow(Cfws, Last_available_seq_num + 1),
    Fun = fun(#cfw{sequence_number = N, status = unknown} = Cfw)
		when N =< Last_available_seq_num ->
		  Cfw#cfw{status = missing};
	     (Cfw) ->
		  Cfw
	  end,
    lists:map(Fun, Cfws1).
    
-spec received_change_set(Cfws1, Seq_num) -> Cfws2 when
      Cfws1 :: nonempty_list(Cfw),
      Cfw :: cfw(),
      Seq_num :: rtps:sequence_number(),
      Cfws2 :: nonempty_list(Cfw).
%% 8.4.10.4.7 received_change_set This operation modifies the status
%% of the ChangeFromWriter that refers to the CacheChange with the
%% SequenceNumber_t ‘a_seq_num.’ The status of the change is set to
%% ‘RECEIVED,’ indicating it has been received.
received_change_set(Cfws, Seq_num) ->
    Cfws1 = grow(Cfws, Seq_num + 1),
    lists:keydelete(Seq_num, #cfw.sequence_number, Cfws1).

%%%===================================================================
%%% Internal functions
%%%===================================================================

%% Extend the cfws if needed by appending cfws with status unknown.
grow(Cfws, Seq_num) ->
    case lists:last(Cfws) of
	#cfw{sequence_number = Last} when Last < Seq_num ->
	    Cfws ++ [#cfw{sequence_number = N, status = unknown}
		     || N <- lists:seq(Last + 1, Seq_num)];
	_ ->
	    Cfws
    end.

%% Shrink the cfws if possible by removing cfws with status lost from
%% the beginning of the list.
shrink([#cfw{status = lost} | [#cfw{status = lost} | _] = Cfws]) ->
    shrink(Cfws);
shrink(Cfws) ->
    Cfws.

